/*
 * Copyright (c) 2001, 2014, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
/*
 * Licensed Materials - Property of IBM
 * RMI-IIOP v1.0
 * Copyright IBM Corp. 1998 1999  All Rights Reserved
 *
 */

package com.sun.corba.se.impl.orbutil;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.DigestOutputStream;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedAction;

import java.lang.reflect.Modifier;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationTargetException;

import java.io.IOException;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InvalidClassException;
import java.io.Serializable;
import java.io.Externalizable;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Hashtable;

import org.omg.CORBA.ValueMember;

import com.sun.corba.se.impl.io.ValueUtility;
import com.sun.corba.se.impl.io.ObjectStreamClass;

/**
 * This is duplicated here to preserve the JDK 1.3.1FCS behavior
 * of calculating the OMG hash code incorrectly when serialPersistentFields
 * is used, but some of the fields no longer exist in the class itself.
 *
 * We have to duplicate it since we aren't allowed to modify the
 * com.sun.corba.se.impl.io version further, and can't make it
 * public outside of its package for security reasons.
 */

/**
 * A ObjectStreamClass_1_3_1 describes a class that can be serialized to a stream
 * or a class that was serialized to a stream.  It contains the name
 * and the serialVersionUID of the class.
 * <br>
 * The ObjectStreamClass_1_3_1 for a specific class loaded in this Java VM can
 * be found using the lookup method.
 *
 * @author Roger Riggs
 * @since JDK1.1
 */
public class ObjectStreamClass_1_3_1 implements java.io.Serializable {

  public static final long kDefaultUID = -1;

  private static Object noArgsList[] = {};
  private static Class<?> noTypesList[] = {};

  private static Hashtable translatedFields;

  /**
   * Find the descriptor for a class that can be serialized.  Null
   * is returned if the specified class does not implement
   * java.io.Serializable or java.io.Externalizable.
   */
  static final ObjectStreamClass_1_3_1 lookup(Class<?> cl) {
    ObjectStreamClass_1_3_1 desc = lookupInternal(cl);
    if (desc.isSerializable() || desc.isExternalizable()) {
      return desc;
    }
    return null;
  }

  /*
   * Find the class descriptor for the specified class.
   * Package access only so it can be called from ObjectIn/OutStream.
   */
  static ObjectStreamClass_1_3_1 lookupInternal(Class<?> cl) {
        /* Synchronize on the hashtable so no two threads will do
         * this at the same time.
         */
    ObjectStreamClass_1_3_1 desc = null;
    synchronized (descriptorFor) {
            /* Find the matching descriptor if it already known */
      desc = findDescriptorFor(cl);
      if (desc != null) {
        return desc;
      }

                /* Check if it's serializable */
      boolean serializable = Serializable.class.isAssignableFrom(cl);
                /* If the class is only Serializable,
                 * lookup the descriptor for the superclass.
                 */
      ObjectStreamClass_1_3_1 superdesc = null;
      if (serializable) {
        Class<?> superclass = cl.getSuperclass();
        if (superclass != null) {
          superdesc = lookup(superclass);
        }
      }

                /* Check if its' externalizable.
                 * If it's Externalizable, clear the serializable flag.
                 * Only one or the other may be set in the protocol.
                 */
      boolean externalizable = false;
      if (serializable) {
        externalizable =
            ((superdesc != null) && superdesc.isExternalizable()) ||
                Externalizable.class.isAssignableFrom(cl);
        if (externalizable) {
          serializable = false;
        }
      }

            /* Create a new version descriptor,
             * it put itself in the known table.
             */
      desc = new ObjectStreamClass_1_3_1(cl, superdesc,
          serializable, externalizable);
    }
    desc.init();
    return desc;
  }

  /**
   * The name of the class described by this descriptor.
   */
  public final String getName() {
    return name;
  }

  /**
   * Return the serialVersionUID for this class.
   * The serialVersionUID defines a set of classes all with the same name
   * that have evolved from a common root class and agree to be serialized
   * and deserialized using a common format.
   */
  public static final long getSerialVersionUID(java.lang.Class<?> clazz) {
    ObjectStreamClass_1_3_1 theosc = ObjectStreamClass_1_3_1.lookup(clazz);
    if (theosc != null) {
      return theosc.getSerialVersionUID();
    }
    return 0;
  }

  /**
   * Return the serialVersionUID for this class.
   * The serialVersionUID defines a set of classes all with the same name
   * that have evolved from a common root class and agree to be serialized
   * and deserialized using a common format.
   */
  public final long getSerialVersionUID() {
    return suid;
  }

  /**
   * Return the serialVersionUID string for this class.
   * The serialVersionUID defines a set of classes all with the same name
   * that have evolved from a common root class and agree to be serialized
   * and deserialized using a common format.
   */
  public final String getSerialVersionUIDStr() {
    if (suidStr == null) {
      suidStr = Long.toHexString(suid).toUpperCase();
    }
    return suidStr;
  }

  /**
   * Return the actual (computed) serialVersionUID for this class.
   */
  public static final long getActualSerialVersionUID(java.lang.Class<?> clazz) {
    ObjectStreamClass_1_3_1 theosc = ObjectStreamClass_1_3_1.lookup(clazz);
    if (theosc != null) {
      return theosc.getActualSerialVersionUID();
    }
    return 0;
  }

  /**
   * Return the actual (computed) serialVersionUID for this class.
   */
  public final long getActualSerialVersionUID() {
    return actualSuid;
  }

  /**
   * Return the actual (computed) serialVersionUID for this class.
   */
  public final String getActualSerialVersionUIDStr() {
    if (actualSuidStr == null) {
      actualSuidStr = Long.toHexString(actualSuid).toUpperCase();
    }
    return actualSuidStr;
  }

  /**
   * Return the class in the local VM that this version is mapped to.
   * Null is returned if there is no corresponding local class.
   */
  public final Class<?> forClass() {
    return ofClass;
  }

  /**
   * Return an array of the fields of this serializable class.
   *
   * @return an array containing an element for each persistent field of this class. Returns an
   * array of length zero if there are no fields.
   * @since JDK1.2
   */
  public ObjectStreamField[] getFields() {
    // Return a copy so the caller can't change the fields.
    if (fields.length > 0) {
      ObjectStreamField[] dup = new ObjectStreamField[fields.length];
      System.arraycopy(fields, 0, dup, 0, fields.length);
      return dup;
    } else {
      return fields;
    }
  }

  public boolean hasField(ValueMember field) {

    for (int i = 0; i < fields.length; i++) {
      try {
        if (fields[i].getName().equals(field.name)) {

          if (fields[i].getSignature().equals(ValueUtility.getSignature(field))) {
            return true;
          }
        }
      } catch (Throwable t) {
      }
    }
    return false;
  }

  /* Avoid unnecessary allocations. */
  final ObjectStreamField[] getFieldsNoCopy() {
    return fields;
  }

  /**
   * Get the field of this class by name.
   *
   * @return The ObjectStreamField object of the named field or null if there is no such named
   * field.
   */
  public final ObjectStreamField getField(String name) {
        /* Binary search of fields by name.
         */
    for (int i = fields.length - 1; i >= 0; i--) {
      if (name.equals(fields[i].getName())) {
        return fields[i];
      }
    }
    return null;
  }

  public Serializable writeReplace(Serializable value) {
    if (writeReplaceObjectMethod != null) {
      try {
        return (Serializable) writeReplaceObjectMethod.invoke(value, noArgsList);
      } catch (Throwable t) {
        throw new RuntimeException(t.getMessage());
      }
    } else {
      return value;
    }
  }

  public Object readResolve(Object value) {
    if (readResolveObjectMethod != null) {
      try {
        return readResolveObjectMethod.invoke(value, noArgsList);
      } catch (Throwable t) {
        throw new RuntimeException(t.getMessage());
      }
    } else {
      return value;
    }
  }

  /**
   * Return a string describing this ObjectStreamClass_1_3_1.
   */
  public final String toString() {
    StringBuffer sb = new StringBuffer();

    sb.append(name);
    sb.append(": static final long serialVersionUID = ");
    sb.append(Long.toString(suid));
    sb.append("L;");
    return sb.toString();
  }

  /*
   * Create a new ObjectStreamClass_1_3_1 from a loaded class.
   * Don't call this directly, call lookup instead.
   */
  private ObjectStreamClass_1_3_1(java.lang.Class<?> cl, ObjectStreamClass_1_3_1 superdesc,
      boolean serial, boolean extern) {
    ofClass = cl;           /* created from this class */

    if (Proxy.isProxyClass(cl)) {
      forProxyClass = true;
    }

    name = cl.getName();
    superclass = superdesc;
    serializable = serial;
    if (!forProxyClass) {
      // proxy classes are never externalizable
      externalizable = extern;
    }

        /*
         * Enter this class in the table of known descriptors.
         * Otherwise, when the fields are read it may recurse
         * trying to find the descriptor for itself.
         */
    insertDescriptorFor(this);

        /*
         * The remainder of initialization occurs in init(), which is called
         * after the lock on the global class descriptor table has been
         * released.
         */
  }

    /*
     * Initialize class descriptor.  This method is only invoked on class
     * descriptors created via calls to lookupInternal().  This method is kept
     * separate from the ObjectStreamClass_1_3_1 constructor so that lookupInternal
     * does not have to hold onto a global class descriptor table lock while the
     * class descriptor is being initialized (see bug 4165204).
     */


  private void init() {
    synchronized (lock) {

      final Class<?> cl = ofClass;

      if (fields != null) // already initialized
      {
        return;
      }

      if (!serializable ||
          externalizable ||
          forProxyClass ||
          name.equals("java.lang.String")) {
        fields = NO_FIELDS;
      } else if (serializable) {

            /* Ask for permission to override field access checks.
             */
        AccessController.doPrivileged(new PrivilegedAction() {
          public Object run() {
                /* Fill in the list of persistent fields.
                 * If it is declared, use the declared serialPersistentFields.
                 * Otherwise, extract the fields from the class itself.
                 */
            try {
              Field pf = cl.getDeclaredField("serialPersistentFields");
              // serial bug 7; the serialPersistentFields were not
              // being read and stored as Accessible bit was not set
              pf.setAccessible(true);
              // serial bug 7; need to find if the field is of type
              // java.io.ObjectStreamField
              java.io.ObjectStreamField[] f =
                  (java.io.ObjectStreamField[]) pf.get(cl);
              int mods = pf.getModifiers();
              if ((Modifier.isPrivate(mods)) &&
                  (Modifier.isStatic(mods)) &&
                  (Modifier.isFinal(mods))) {
                fields = (ObjectStreamField[]) translateFields((Object[]) pf.get(cl));
              }
            } catch (NoSuchFieldException e) {
              fields = null;
            } catch (IllegalAccessException e) {
              fields = null;
            } catch (IllegalArgumentException e) {
              fields = null;
            } catch (ClassCastException e) {
                    /* Thrown if a field serialPersistentField exists
                     * but it is not of type ObjectStreamField.
                     */
              fields = null;
            }

            if (fields == null) {
                    /* Get all of the declared fields for this
                     * Class. setAccessible on all fields so they
                     * can be accessed later.  Create a temporary
                     * ObjectStreamField array to hold each
                     * non-static, non-transient field. Then copy the
                     * temporary array into an array of the correct
                     * size once the number of fields is known.
                     */
              Field[] actualfields = cl.getDeclaredFields();

              int numFields = 0;
              ObjectStreamField[] tempFields =
                  new ObjectStreamField[actualfields.length];
              for (int i = 0; i < actualfields.length; i++) {
                int modifiers = actualfields[i].getModifiers();
                if (!Modifier.isStatic(modifiers) &&
                    !Modifier.isTransient(modifiers)) {
                  tempFields[numFields++] =
                      new ObjectStreamField(actualfields[i]);
                }
              }
              fields = new ObjectStreamField[numFields];
              System.arraycopy(tempFields, 0, fields, 0, numFields);

            } else {
              // For each declared persistent field, look for an actual
              // reflected Field. If there is one, make sure it's the correct
              // type and cache it in the ObjectStreamClass_1_3_1 for that field.
              for (int j = fields.length - 1; j >= 0; j--) {
                try {
                  Field reflField = cl.getDeclaredField(fields[j].getName());
                  if (fields[j].getType() == reflField.getType()) {
                    // reflField.setAccessible(true);
                    fields[j].setField(reflField);
                  }
                } catch (NoSuchFieldException e) {
                  // Nothing to do
                }
              }
            }
            return null;
          }
        });

        if (fields.length > 1) {
          Arrays.sort(fields);
        }

            /* Set up field data for use while writing using the API api. */
        computeFieldInfo();
      }

        /* Get the serialVersionUID from the class.
         * It uses the access override mechanism so make sure
         * the field objects is only used here.
         *
         * NonSerializable classes have a serialVerisonUID of 0L.
         */
      if (isNonSerializable()) {
        suid = 0L;
      } else {
        // Lookup special Serializable members using reflection.
        AccessController.doPrivileged(new PrivilegedAction() {
          public Object run() {
            if (forProxyClass) {
              // proxy classes always have serialVersionUID of 0L
              suid = 0L;
            } else {
              try {
                final Field f = cl.getDeclaredField("serialVersionUID");
                int mods = f.getModifiers();
                // SerialBug 5:  static final SUID should be read
                if (Modifier.isStatic(mods) &&
                    Modifier.isFinal(mods)) {
                  f.setAccessible(true);
                  suid = f.getLong(cl);
                  // get rid of native code
                  // suid = getSerialVersionUIDField(cl);
                  // SerialBug 2: should be computed after writeObject
                  // actualSuid = computeStructuralUID(cl);
                } else {
                  suid = ObjectStreamClass.getSerialVersionUID(cl);
                  // SerialBug 2: should be computed after writeObject
                  // actualSuid = computeStructuralUID(cl);
                }
              } catch (NoSuchFieldException ex) {
                suid = ObjectStreamClass.getSerialVersionUID(cl);
                // SerialBug 2: should be computed after writeObject
                // actualSuid = computeStructuralUID(cl);
              } catch (IllegalAccessException ex) {
                suid = ObjectStreamClass.getSerialVersionUID(cl);
              }
            }

            try {
              writeReplaceObjectMethod = cl.getDeclaredMethod("writeReplace", noTypesList);
              if (Modifier.isStatic(writeReplaceObjectMethod.getModifiers())) {
                writeReplaceObjectMethod = null;
              } else {
                writeReplaceObjectMethod.setAccessible(true);
              }

            } catch (NoSuchMethodException e2) {

            }

            try {
              readResolveObjectMethod = cl.getDeclaredMethod("readResolve", noTypesList);
              if (Modifier.isStatic(readResolveObjectMethod.getModifiers())) {
                readResolveObjectMethod = null;
              } else {
                readResolveObjectMethod.setAccessible(true);
              }

            } catch (NoSuchMethodException e2) {

            }

                /* Cache lookup of writeObject and readObject for
                 * Serializable classes. (Do not lookup for
                 * Externalizable)
                 */

            if (serializable && !forProxyClass) {

                    /* Look for the writeObject method
                     * Set the accessible flag on it here. ObjectOutputStream
                     * will call it as necessary.
                     */
              try {
                Class<?>[] args = {java.io.ObjectOutputStream.class};
                writeObjectMethod = cl.getDeclaredMethod("writeObject", args);
                hasWriteObjectMethod = true;
                int mods = writeObjectMethod.getModifiers();

                // Method must be private and non-static
                if (!Modifier.isPrivate(mods) ||
                    Modifier.isStatic(mods)) {
                  writeObjectMethod = null;
                  hasWriteObjectMethod = false;
                }

              } catch (NoSuchMethodException e) {
              }

                    /* Look for the readObject method
                     * set the access override and save the reference for
                     * ObjectInputStream so it can all the method directly.
                     */
              try {
                Class<?>[] args = {java.io.ObjectInputStream.class};
                readObjectMethod = cl.getDeclaredMethod("readObject", args);
                int mods = readObjectMethod.getModifiers();

                // Method must be private and non-static
                if (!Modifier.isPrivate(mods) ||
                    Modifier.isStatic(mods)) {
                  readObjectMethod = null;
                }
              } catch (NoSuchMethodException e) {
              }
              // Compute the structural UID.  This must be done after the
              // calculation for writeObject.  Fixed 4/20/2000, eea1
              // SerialBug 2: to have correct value in RepId
            }
            return null;
          }
        });
      }

      actualSuid = computeStructuralUID(this, cl);
    }

  }

  /*
   * Create an empty ObjectStreamClass_1_3_1 for a class about to be read.
   * This is separate from read so ObjectInputStream can assign the
   * wire handle early, before any nested ObjectStreamClass_1_3_1 might
   * be read.
   */
  ObjectStreamClass_1_3_1(String n, long s) {
    name = n;
    suid = s;
    superclass = null;
  }

  private static Object[] translateFields(Object objs[])
      throws NoSuchFieldException {
    try {
      java.io.ObjectStreamField fields[] = (java.io.ObjectStreamField[]) objs;
      Object translation[] = null;

      if (translatedFields == null) {
        translatedFields = new Hashtable();
      }

      translation = (Object[]) translatedFields.get(fields);

      if (translation != null) {
        return translation;
      } else {
        Class<?> osfClass = com.sun.corba.se.impl.orbutil.ObjectStreamField.class;

        translation = (Object[]) java.lang.reflect.Array.newInstance(osfClass, objs.length);
        Object arg[] = new Object[2];
        Class<?> types[] = {String.class, Class.class};
        Constructor constructor = osfClass.getDeclaredConstructor(types);
        for (int i = fields.length - 1; i >= 0; i--) {
          arg[0] = fields[i].getName();
          arg[1] = fields[i].getType();

          translation[i] = constructor.newInstance(arg);
        }
        translatedFields.put(fields, translation);

      }

      return (Object[]) translation;
    } catch (Throwable t) {
      throw new NoSuchFieldException();
    }
  }

  /* Compare the base class names of streamName and localName.
   *
   * @return  Return true iff the base class name compare.
   * @parameter streamName    Fully qualified class name.
   * @parameter localName     Fully qualified class name.
   * @parameter pkgSeparator  class names use either '.' or '/'.
   *
   * Only compare base class name to allow package renaming.
   */
  static boolean compareClassNames(String streamName,
      String localName,
      char pkgSeparator) {
        /* compare the class names, stripping off package names. */
    int streamNameIndex = streamName.lastIndexOf(pkgSeparator);
    if (streamNameIndex < 0) {
      streamNameIndex = 0;
    }

    int localNameIndex = localName.lastIndexOf(pkgSeparator);
    if (localNameIndex < 0) {
      localNameIndex = 0;
    }

    return streamName.regionMatches(false, streamNameIndex,
        localName, localNameIndex,
        streamName.length() - streamNameIndex);
  }

  /*
   * Compare the types of two class descriptors.
   * They match if they have the same class name and suid
   */
  final boolean typeEquals(ObjectStreamClass_1_3_1 other) {
    return (suid == other.suid) &&
        compareClassNames(name, other.name, '.');
  }

  /*
   * Return the superclass descriptor of this descriptor.
   */
  final void setSuperclass(ObjectStreamClass_1_3_1 s) {
    superclass = s;
  }

  /*
   * Return the superclass descriptor of this descriptor.
   */
  final ObjectStreamClass_1_3_1 getSuperclass() {
    return superclass;
  }

  /*
   * Return whether the class has a writeObject method
   */
  final boolean hasWriteObject() {
    return hasWriteObjectMethod;
  }

  final boolean isCustomMarshaled() {
    return (hasWriteObject() || isExternalizable());
  }

  /*
   * Return true if all instances of 'this' Externalizable class
   * are written in block-data mode from the stream that 'this' was read
   * from. <p>
   *
   * In JDK 1.1, all Externalizable instances are not written
   * in block-data mode.
   * In JDK 1.2, all Externalizable instances, by default, are written
   * in block-data mode and the Externalizable instance is terminated with
   * tag TC_ENDBLOCKDATA. Change enabled the ability to skip Externalizable
   * instances.
   *
   * IMPLEMENTATION NOTE:
   *   This should have been a mode maintained per stream; however,
   *   for compatibility reasons, it was only possible to record
   *   this change per class. All Externalizable classes within
   *   a given stream should either have this mode enabled or
   *   disabled. This is enforced by not allowing the PROTOCOL_VERSION
   *   of a stream to he changed after any objects have been written.
   *
   * @see ObjectOutputStream#useProtocolVersion
   * @see ObjectStreamConstants#PROTOCOL_VERSION_1
   * @see ObjectStreamConstants#PROTOCOL_VERSION_2
   *
   * @since JDK 1.2
   */
  boolean hasExternalizableBlockDataMode() {
    return hasExternalizableBlockData;
  }

  /*
   * Return the ObjectStreamClass_1_3_1 of the local class this one is based on.
   */
  final ObjectStreamClass_1_3_1 localClassDescriptor() {
    return localClassDesc;
  }

  /*
   * Get the Serializability of the class.
   */
  boolean isSerializable() {
    return serializable;
  }

  /*
   * Get the externalizability of the class.
   */
  boolean isExternalizable() {
    return externalizable;
  }

  boolean isNonSerializable() {
    return !(externalizable || serializable);
  }

  /*
   * Calculate the size of the array needed to store primitive data and the
   * number of object references to read when reading from the input
   * stream.
   */
  private void computeFieldInfo() {
    primBytes = 0;
    objFields = 0;

    for (int i = 0; i < fields.length; i++) {
      switch (fields[i].getTypeCode()) {
        case 'B':
        case 'Z':
          primBytes += 1;
          break;
        case 'C':
        case 'S':
          primBytes += 2;
          break;

        case 'I':
        case 'F':
          primBytes += 4;
          break;
        case 'J':
        case 'D':
          primBytes += 8;
          break;

        case 'L':
        case '[':
          objFields += 1;
          break;
      }
    }
  }

  private static long computeStructuralUID(ObjectStreamClass_1_3_1 osc, Class<?> cl) {
    ByteArrayOutputStream devnull = new ByteArrayOutputStream(512);

    long h = 0;
    try {

      if ((!java.io.Serializable.class.isAssignableFrom(cl)) ||
          (cl.isInterface())) {
        return 0;
      }

      if (java.io.Externalizable.class.isAssignableFrom(cl)) {
        return 1;
      }

      MessageDigest md = MessageDigest.getInstance("SHA");
      DigestOutputStream mdo = new DigestOutputStream(devnull, md);
      DataOutputStream data = new DataOutputStream(mdo);

      // Get SUID of parent
      Class<?> parent = cl.getSuperclass();
      if ((parent != null))
      // SerialBug 1; acc. to spec the one for
      // java.lang.object
      // should be computed and put
      //     && (parent != java.lang.Object.class))
      {
        //data.writeLong(computeSerialVersionUID(null,parent));
        data.writeLong(computeStructuralUID(lookup(parent), parent));
      }

      if (osc.hasWriteObject()) {
        data.writeInt(2);
      } else {
        data.writeInt(1);
      }

            /* Sort the field names to get a deterministic order */
      // Field[] field = ObjectStreamClass_1_3_1.getDeclaredFields(cl);

      ObjectStreamField[] fields = osc.getFields();

      // Must make sure that the Field array we allocate
      // below is exactly the right size.  Bug fix for
      // 4397133.
      int numNonNullFields = 0;
      for (int i = 0; i < fields.length; i++) {
        if (fields[i].getField() != null) {
          numNonNullFields++;
        }
      }

      Field[] field = new java.lang.reflect.Field[numNonNullFields];
      for (int i = 0, fieldNum = 0; i < fields.length; i++) {
        if (fields[i].getField() != null) {
          field[fieldNum++] = fields[i].getField();
        }
      }

      if (field.length > 1) {
        Arrays.sort(field, compareMemberByName);
      }

      for (int i = 0; i < field.length; i++) {
        Field f = field[i];

                                /* Include in the hash all fields except those that are
                                 * transient
                                 */
        int m = f.getModifiers();
        //Serial 6
        //if (Modifier.isTransient(m) || Modifier.isStatic(m))
        // spec reference 00-01-06.pdf, 1.3.5.6, states non-static
        // non-transient, public fields are mapped to Java IDL.
        //
        // Here's the quote from the first paragraph:
        // Java non-static non-transient public fields are mapped to
        // OMG IDL public data members, and other Java fields are
        // not mapped.

        // if (Modifier.isTransient(m) || Modifier.isStatic(m))
        //     continue;

        data.writeUTF(f.getName());
        data.writeUTF(getSignature(f.getType()));
      }

            /* Compute the hash value for this class.
             * Use only the first 64 bits of the hash.
             */
      data.flush();
      byte hasharray[] = md.digest();
      // int minimum = Math.min(8, hasharray.length);
      // SerialBug 3: SHA computation is wrong; for loop reversed
      //for (int i = minimum; i > 0; i--)
      for (int i = 0; i < Math.min(8, hasharray.length); i++) {
        h += (long) (hasharray[i] & 255) << (i * 8);
      }
    } catch (IOException ignore) {
            /* can't happen, but be deterministic anyway. */
      h = -1;
    } catch (NoSuchAlgorithmException complain) {
      throw new SecurityException(complain.getMessage());
    }
    return h;
  }

  /**
   * Compute the JVM signature for the class.
   */
  static String getSignature(Class<?> clazz) {
    String type = null;
    if (clazz.isArray()) {
      Class<?> cl = clazz;
      int dimensions = 0;
      while (cl.isArray()) {
        dimensions++;
        cl = cl.getComponentType();
      }
      StringBuffer sb = new StringBuffer();
      for (int i = 0; i < dimensions; i++) {
        sb.append("[");
      }
      sb.append(getSignature(cl));
      type = sb.toString();
    } else if (clazz.isPrimitive()) {
      if (clazz == Integer.TYPE) {
        type = "I";
      } else if (clazz == Byte.TYPE) {
        type = "B";
      } else if (clazz == Long.TYPE) {
        type = "J";
      } else if (clazz == Float.TYPE) {
        type = "F";
      } else if (clazz == Double.TYPE) {
        type = "D";
      } else if (clazz == Short.TYPE) {
        type = "S";
      } else if (clazz == Character.TYPE) {
        type = "C";
      } else if (clazz == Boolean.TYPE) {
        type = "Z";
      } else if (clazz == Void.TYPE) {
        type = "V";
      }
    } else {
      type = "L" + clazz.getName().replace('.', '/') + ";";
    }
    return type;
  }

  /*
   * Compute the JVM method descriptor for the method.
   */
  static String getSignature(Method meth) {
    StringBuffer sb = new StringBuffer();

    sb.append("(");

    Class<?>[] params = meth.getParameterTypes(); // avoid clone
    for (int j = 0; j < params.length; j++) {
      sb.append(getSignature(params[j]));
    }
    sb.append(")");
    sb.append(getSignature(meth.getReturnType()));
    return sb.toString();
  }

  /*
   * Compute the JVM constructor descriptor for the constructor.
   */
  static String getSignature(Constructor cons) {
    StringBuffer sb = new StringBuffer();

    sb.append("(");

    Class<?>[] params = cons.getParameterTypes(); // avoid clone
    for (int j = 0; j < params.length; j++) {
      sb.append(getSignature(params[j]));
    }
    sb.append(")V");
    return sb.toString();
  }

  /*
   * Cache of Class -> ClassDescriptor Mappings.
   */
  static private ObjectStreamClassEntry[] descriptorFor = new ObjectStreamClassEntry[61];

  /*
   * findDescriptorFor a Class.  This looks in the cache for a
   * mapping from Class -> ObjectStreamClass mappings.  The hashCode
   * of the Class is used for the lookup since the Class is the key.
   * The entries are extended from java.lang.ref.SoftReference so the
   * gc will be able to free them if needed.
   */
  private static ObjectStreamClass_1_3_1 findDescriptorFor(Class<?> cl) {

    int hash = cl.hashCode();
    int index = (hash & 0x7FFFFFFF) % descriptorFor.length;
    ObjectStreamClassEntry e;
    ObjectStreamClassEntry prev;

        /* Free any initial entries whose refs have been cleared */
    while ((e = descriptorFor[index]) != null && e.get() == null) {
      descriptorFor[index] = e.next;
    }

        /* Traverse the chain looking for a descriptor with ofClass == cl.
         * unlink entries that are unresolved.
         */
    prev = e;
    while (e != null) {
      ObjectStreamClass_1_3_1 desc = (ObjectStreamClass_1_3_1) (e.get());
      if (desc == null) {
        // This entry has been cleared,  unlink it
        prev.next = e.next;
      } else {
        if (desc.ofClass == cl) {
          return desc;
        }
        prev = e;
      }
      e = e.next;
    }
    return null;
  }

  /*
   * insertDescriptorFor a Class -> ObjectStreamClass_1_3_1 mapping.
   */
  private static void insertDescriptorFor(ObjectStreamClass_1_3_1 desc) {
    // Make sure not already present
    if (findDescriptorFor(desc.ofClass) != null) {
      return;
    }

    int hash = desc.ofClass.hashCode();
    int index = (hash & 0x7FFFFFFF) % descriptorFor.length;
    ObjectStreamClassEntry e = new ObjectStreamClassEntry(desc);
    e.next = descriptorFor[index];
    descriptorFor[index] = e;
  }

  private static Field[] getDeclaredFields(final Class clz) {
    return (Field[]) AccessController.doPrivileged(new PrivilegedAction() {
      public Object run() {
        return clz.getDeclaredFields();
      }
    });
  }


  /*
   * The name of this descriptor
   */
  private String name;

  /*
   * The descriptor of the supertype.
   */
  private ObjectStreamClass_1_3_1 superclass;

  /*
   * Flags for Serializable and Externalizable.
   */
  private boolean serializable;
  private boolean externalizable;

  /*
   * Array of persistent fields of this class, sorted by
   * type and name.
   */
  private ObjectStreamField[] fields;

  /*
   * Class that is a descriptor for in this virtual machine.
   */
  private Class<?> ofClass;

  /*
   * True if descriptor for a proxy class.
   */
  boolean forProxyClass;


  /*
   * SerialVersionUID for this class.
   */
  private long suid = kDefaultUID;
  private String suidStr = null;

  /*
   * Actual (computed) SerialVersionUID for this class.
   */
  private long actualSuid = kDefaultUID;
  private String actualSuidStr = null;

  /*
   * The total number of bytes of primitive fields.
   * The total number of object fields.
   */
  int primBytes;
  int objFields;

  /* Internal lock object. */
  private Object lock = new Object();

  /* True if this class has/had a writeObject method */
  private boolean hasWriteObjectMethod;

  /* In JDK 1.1, external data was not written in block mode.
   * As of JDK 1.2, external data is written in block data mode. This
   * flag enables JDK 1.2 to be able to read JDK 1.1 written external data.
   *
   * @since JDK 1.2
   */
  private boolean hasExternalizableBlockData;
  Method writeObjectMethod;
  Method readObjectMethod;
  private transient Method writeReplaceObjectMethod;
  private transient Method readResolveObjectMethod;

  /*
   * ObjectStreamClass_1_3_1 that this one was built from.
   */
  private ObjectStreamClass_1_3_1 localClassDesc;

    /* Get the private static final field for serial version UID */
  // private static native long getSerialVersionUIDField(Class cl);

  /**
   * use serialVersionUID from JDK 1.1. for interoperability
   */
  private static final long serialVersionUID = -6120832682080437368L;

  /**
   * Set serialPersistentFields of a Serializable class to this value to
   * denote that the class has no Serializable fields.
   */
  public static final ObjectStreamField[] NO_FIELDS =
      new ObjectStreamField[0];

  /*
   * Entries held in the Cache of known ObjectStreamClass_1_3_1 objects.
   * Entries are chained together with the same hash value (modulo array size).
   */
  private static class ObjectStreamClassEntry // extends java.lang.ref.SoftReference
  {

    ObjectStreamClassEntry(ObjectStreamClass_1_3_1 c) {
      //super(c);
      this.c = c;
    }

    ObjectStreamClassEntry next;

    public Object get() {
      return c;
    }

    private ObjectStreamClass_1_3_1 c;
  }

  /*
   * Comparator object for Classes and Interfaces
   */
  private static Comparator compareClassByName =
      new CompareClassByName();

  private static class CompareClassByName implements Comparator {

    public int compare(Object o1, Object o2) {
      Class<?> c1 = (Class) o1;
      Class<?> c2 = (Class) o2;
      return (c1.getName()).compareTo(c2.getName());
    }
  }

  /*
   * Comparator object for Members, Fields, and Methods
   */
  private static Comparator compareMemberByName =
      new CompareMemberByName();

  private static class CompareMemberByName implements Comparator {

    public int compare(Object o1, Object o2) {
      String s1 = ((Member) o1).getName();
      String s2 = ((Member) o2).getName();

      if (o1 instanceof Method) {
        s1 += getSignature((Method) o1);
        s2 += getSignature((Method) o2);
      } else if (o1 instanceof Constructor) {
        s1 += getSignature((Constructor) o1);
        s2 += getSignature((Constructor) o2);
      }
      return s1.compareTo(s2);
    }
  }

  /* It is expensive to recompute a method or constructor signature
     many times, so compute it only once using this data structure. */
  private static class MethodSignature implements Comparator {

    Member member;
    String signature;      // cached parameter signature

    /* Given an array of Method or Constructor members,
       return a sorted array of the non-private members.*/
        /* A better implementation would be to implement the returned data
           structure as an insertion sorted link list.*/
    static MethodSignature[] removePrivateAndSort(Member[] m) {
      int numNonPrivate = 0;
      for (int i = 0; i < m.length; i++) {
        if (!Modifier.isPrivate(m[i].getModifiers())) {
          numNonPrivate++;
        }
      }
      MethodSignature[] cm = new MethodSignature[numNonPrivate];
      int cmi = 0;
      for (int i = 0; i < m.length; i++) {
        if (!Modifier.isPrivate(m[i].getModifiers())) {
          cm[cmi] = new MethodSignature(m[i]);
          cmi++;
        }
      }
      if (cmi > 0) {
        Arrays.sort(cm, cm[0]);
      }
      return cm;
    }

    /* Assumes that o1 and o2 are either both methods
       or both constructors.*/
    public int compare(Object o1, Object o2) {
            /* Arrays.sort calls compare when o1 and o2 are equal.*/
      if (o1 == o2) {
        return 0;
      }

      MethodSignature c1 = (MethodSignature) o1;
      MethodSignature c2 = (MethodSignature) o2;

      int result;
      if (isConstructor()) {
        result = c1.signature.compareTo(c2.signature);
      } else { // is a Method.
        result = c1.member.getName().compareTo(c2.member.getName());
        if (result == 0) {
          result = c1.signature.compareTo(c2.signature);
        }
      }
      return result;
    }

    final private boolean isConstructor() {
      return member instanceof Constructor;
    }

    private MethodSignature(Member m) {
      member = m;
      if (isConstructor()) {
        signature = ObjectStreamClass_1_3_1.getSignature((Constructor) m);
      } else {
        signature = ObjectStreamClass_1_3_1.getSignature((Method) m);
      }
    }
  }
}
